#include "n2.h"
#include <iostream>
#include <sstream>
#include <fstream>


Kernel::Kernel(unsigned size, std::string sourceName, TextureParameter &param)
: texSize(size), shaderSourceName(sourceName), texParam(param)
{
}
void
Kernel::init()
{
	makeTextureset();
	loadShader(shaderSourceName);
	initCG();
}
void
Kernel::calc(float *bodies, float *forces, float eps)
{
	static GLenum output = GL_COLOR_ATTACHMENT0_EXT;

	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, 
		output, texParam.target, forcesTexID, 0);	
	checkFramebufferStatus();

    cgGLEnableProfile(fragmentProfile);
    cgGLBindProgram(fragmentProgram);

	// assign vector parameters
	writeToTexture(bodiesTexID, bodies);
	cgGLSetTextureParameter(bodiesParam, bodiesTexID);
    cgGLEnableTextureParameter(bodiesParam);

	// assign scalar parameters
	cgSetParameter1f(sizeParam, texSize);
	cgSetParameter1f(epsParam, eps);

	// render
	glDrawBuffer(output);
    glBegin(GL_QUADS);
    glTexCoord2f(0.0, 0.0); 
    glVertex2f(0.0, 0.0);
    glTexCoord2f(texSize, 0.0); 
    glVertex2f(texSize, 0.0);
    glTexCoord2f(texSize, texSize); 
    glVertex2f(texSize, texSize);
    glTexCoord2f(0.0, texSize); 
    glVertex2f(0.0, texSize);
    glEnd();

	checkFramebufferStatus();
	checkGLError();

	readFromTexture(forcesTexID, output, forces);
}
void
Kernel::loadShader(std::string &fileName)
{
	std::ifstream input(fileName.c_str());

	char buf[1024];
	std::stringstream ss;
	while (input) {
		input.getline(buf, sizeof(buf));
		ss << buf;
		if (input.eof())
			break;
	}
	shaderSourceCode = ss.str();
}
void
Kernel::makeTextureset()
{
	glGenTextures(1, &bodiesTexID);
	glGenTextures(1, &forcesTexID);

	makeTexture(bodiesTexID);
	makeTexture(forcesTexID);

	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
	checkGLError();
}
void
Kernel::cgErrorCallback()
{
    CGerror lastError = cgGetError();
    throw cgGetErrorString(lastError);
}
void
Kernel::checkCgError()
{
	std::stringstream ss;
	CGerror error;
	const char *strError = cgGetLastErrorString(&error);

	if (error != CG_NO_ERROR) {
		ss << "cg_erro_no=" << error << ": " << strError;

		std::cerr << ss.str() << std::endl;
		exit(1);
	}
}
void
Kernel::checkFramebufferStatus()
{
    GLenum status = (GLenum) 
		glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
	if (status == GL_FRAMEBUFFER_COMPLETE_EXT)
		return;

	throw "glFramebufferTexture2DEXT() failed";
}
void
Kernel::checkGLError()
{
    GLenum errCode = glGetError();
    if (errCode == GL_NO_ERROR)
		return;
	
	throw gluErrorString(errCode);
}
void
Kernel::initCG()
{
    cgSetErrorCallback(&Kernel::cgErrorCallback);
	checkCgError();
    cgContext = cgCreateContext();
    fragmentProfile = cgGLGetLatestProfile(CG_GL_FRAGMENT);
    cgGLSetOptimalOptions(fragmentProfile);

    fragmentProgram = cgCreateProgram (cgContext, CG_SOURCE, 
		shaderSourceCode.c_str(), fragmentProfile, 
		"interaction", NULL);
    cgGLLoadProgram (fragmentProgram);

	bodiesParam = cgGetNamedParameter(fragmentProgram, "bodies");
    forcesParam = cgGetNamedParameter(fragmentProgram, "forces");
    sizeParam = cgGetNamedParameter(fragmentProgram, "size");
    epsParam = cgGetNamedParameter(fragmentProgram, "eps");
}
void
Kernel::makeTexture(const GLuint texID)
{
    glBindTexture(texParam.target, texID);
    glTexParameteri(texParam.target, 
		GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(texParam.target, 
		GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(texParam.target, 
		GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(texParam.target, 
		GL_TEXTURE_WRAP_T, GL_CLAMP);

	glTexImage2D(texParam.target, 0, texParam.iformat, 
		texSize, texSize, 0, texParam.format, GL_FLOAT, 0);

	checkGLError();
}
void
Kernel::writeToTexture(GLuint texID, float *data)
{
    glBindTexture(texParam.target, texID);
    glTexSubImage2D(texParam.target, 0, 0, 0, 
		texSize, texSize, texParam.format, GL_FLOAT, data);
}
void
Kernel::readFromTexture(GLuint texID, GLenum attach, float *data)
{
    glReadBuffer(attach);
    glReadPixels(0, 0, texSize, texSize, 
		texParam.format, GL_FLOAT, data);
}



GPGPU::GPGPU(unsigned texSize)
: texSize(texSize)
{
}
GPGPU::~GPGPU()
{
    glDeleteFramebuffersEXT(1,&framebuffer);
    glutDestroyWindow(glutWindowHandle);
}
void
GPGPU::init(int &argc, char **argv)
{
	initGLUT(argc, argv);
	initGLEW();
	initFBO();
}
void
GPGPU::initGLUT(int &argc, char **argv)
{
	glutInitWindowSize(texSize, texSize);
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGBA);
	glutWindowHandle = glutCreateWindow("Hello");
}
void
GPGPU::initGLEW()
{
	int err = glewInit();

	if (err == GLEW_OK)
		return;

	throw glewGetErrorString(err);
}
void
GPGPU::initFBO()
{
    glGenFramebuffersEXT(1, &framebuffer);
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer);

	glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0.0, texSize, 0.0, texSize);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glViewport(0, 0, texSize, texSize);
}
